Repository navigation
feat(store): enforce unique names and emails over live rows only - #1928
Conversation
Organization name, project name, user email, user name, domain name per org, and group name per org were plain unique constraints. Once rows are soft-deleted they keep their values, so a deleted row would block the name or email forever. Each constraint is replaced by a unique index that only covers rows where deleted_at is null. A live row still cannot share a name or email with another live row. Deleted rows no longer take part in the check. Roles, resources, relations, and policies keep their plain constraints for now. Their repositories upsert with ON CONFLICT on those columns, so they need a code change alongside the index and will follow separately.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review. 📝 SummarySummary by CodeRabbit
WalkthroughThe migration enforces uniqueness only for non-deleted records across organizations, projects, users, domains, and groups. The rollback restores standard unique constraints. ChangesLive uniqueness enforcement
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Change: Feature Merge Risk: ⚪ Minimal · up to The migration has no verified merge-blocking issue in the available evidence; rollback behavior with reused values remains unverified. 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
Warning Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Coverage Report for CI Build 35071715356Coverage remained the same at 50.229%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
What
Replaces six plain unique constraints with unique indexes that only cover live rows, meaning rows where
deleted_at IS NULL:organizations_name_keyuq_organizations_name_liveprojects_name_keyuq_projects_name_liveusers_email_keyuq_users_email_liveusers_name_keyuq_users_name_liveorg_id_name_uniqueuq_domains_org_id_name_livegroups_org_id_name_keyuq_groups_org_id_name_liveWhy
Soft-deleted rows keep their values. With a plain unique constraint, a deleted organization would hold its name forever, and a person who deleted their account could never sign up again with the same email. Product confirmed that names and emails should be reusable once the old row is deleted. Uniqueness is still enforced among live rows.
Primary keys are untouched, so ids stay unique across live and deleted rows.
Not in this PR
Roles, resources, relations, and policies keep their plain constraints. Their repositories upsert with
ON CONFLICTon those columns. A partial unique index only satisfiesON CONFLICTwhen the clause repeats the sameWHEREpredicate, so those four need a code change alongside the index. They will follow separately.Behaviour change
None today, because no row has
deleted_atset yet. Once soft delete starts writing, a deleted name or email can be reused by a new row. Duplicate detection in the repositories keys off SQL state23505, which a unique index raises the same way a constraint does.Rollback note
The down migration restores the plain constraints. It will fail if two rows, live or deleted, share a value by then. That is expected and is the reason the read filters must be deployed before any soft-delete write.
Tested
duplicate key value violates unique constraint "uq_..._live").go test -race ./internal/store/postgres/passes against Docker Postgres.